/**
 * Broadcast.java
 *
 * Created on Oct 14, 2010, 4:47:24 PM
 *
 * @author pquiring
 *
 * This class implements Broadcasting a pre-recorded message to a list of phone numbers and/or taking surveys.
 *
 * NOTE : Please only broadcast to your own customers.  Laws in some countries/states may prohibit contacting random individuals.
 *
 */

import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;

import javaforce.*;
import javaforce.voip.*;

public class Broadcast extends javax.swing.JFrame implements SIPClientInterface, RTPInterface, ActionListener {

  public final String version = "0.37";

  public String startList = null;
  public String cfgSuffix = "";
  public boolean allowdups = false;
  public JFLockFile lockFile;
  public String databaseName = "jfBroadcastSchema2";
  public String dbPath;

  /** Creates new form Broadcast */
  public Broadcast() {
    processArgs();
    lockFile = new JFLockFile();
    if (!lockFile.lock(JF.getUserPath() + "/jfbroadcast" + cfgSuffix + ".lck")) {
      JFAWT.showError("Error", "Another instance of jfBroadcast is already running!");
      System.exit(0);
    }
    dbPath = null;
    if (dbPath == null) {
      dbPath = System.getProperty("user.home");
    }
    //setup derby.system.home
    System.setProperty("derby.system.home", dbPath);
    SQL.initClass(SQL.derbySQL);
    initComponents();
    setPosition();
    loadHelp();
    if (cfgSuffix.length() > 0)
      setTitle("jfBroadcast/" + version + ":" + cfgSuffix);
    else
      setTitle("jfBroadcast/" + version);
    JFLog.init(JF.getUserPath() + "/jfbroadcast" + cfgSuffix + ".log", true);
    loadSetup();
    setOptsState();
    updateAvailableLists();
    new Thread() {
      @Override
      public void run() {
        createDatabase();
        if (check_update.isSelected()) checkVersion();
      }
    }.start();
    idx = 0;
    buttonGroup1.add(single);
    buttonGroup1.add(multi);
    buttonGroup1.add(terminate);
    buttonGroup1.add(gotouser);
    buttonGroup1.add(cont);
    buttonGroup1.add(gotoidx);
    buttonGroup1.add(xfer);
    loadQuestion();
    if (startList != null) {
      //do -start command line option
      int cnt = selected_list.getItemCount();
      boolean ok = false;
      for(int b=0;b<cnt;b++) {
        String item = (String)selected_list.getItemAt(b);
        if (item.equals(startList)) {
          selected_list.setSelectedIndex(b);
          start.doClick();
          ok = true;
          break;
        }
      }
      if (!ok) {
        JFLog.log("Error:specifed list for -start option not found");
      }
    }
    //set icons
    JFImage appicon = new JFImage();
    appicon.loadPNG(this.getClass().getClassLoader().getResourceAsStream("jfbroadcast.png"));
    setIconImage(appicon.getImage());
    appicon.loadPNG(this.getClass().getClassLoader().getResourceAsStream("jfbroadcast_tray.png"));
    tray = SystemTray.getSystemTray();
    Dimension size = tray.getTrayIconSize();
    JFImage scaled = new JFImage(size.width, size.height);
    scaled.fill(0, 0, size.width, size.height, 0x00000000, true);  //fill with alpha transparent
    if (true) {
      //scaled image (looks bad sometimes)
      scaled.getGraphics().drawImage(appicon.getImage()
        , 0, 0, size.width, size.height
        , 0, 0, appicon.getWidth(), appicon.getHeight()
        , null);
    } else {
      //center image
      scaled.getGraphics().drawImage(appicon.getImage()
        , (size.width - appicon.getWidth()) / 2
        , (size.height - appicon.getHeight()) / 2
        , null);
    }
    //create tray icon
    PopupMenu popup = new PopupMenu();
    show = new MenuItem("Show");
    show.addActionListener(this);
    popup.add(show);
    popup.addSeparator();
    exit = new MenuItem("Exit");
    exit.addActionListener(this);
    popup.add(exit);
    icon = new TrayIcon(scaled.getImage(), "jfBroadcast", popup);
    icon.addActionListener(this);
    tray = SystemTray.getSystemTray();
    try { tray.add(icon); } catch (Exception e) { JFLog.log(e); }
  }

  /** This method is called from within the constructor to
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
  @SuppressWarnings("unchecked")
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    buttonGroup1 = new javax.swing.ButtonGroup();
    jLabel1 = new javax.swing.JLabel();
    start = new javax.swing.JButton();
    status = new javax.swing.JLabel();
    jTabbedPane1 = new javax.swing.JTabbedPane();
    jPanel2 = new javax.swing.JPanel();
    jLabel9 = new javax.swing.JLabel();
    jLabel10 = new javax.swing.JLabel();
    jLabel11 = new javax.swing.JLabel();
    jLabel12 = new javax.swing.JLabel();
    sip_user = new javax.swing.JTextField();
    sip_auth = new javax.swing.JTextField();
    sip_host = new javax.swing.JTextField();
    jLabel34 = new javax.swing.JLabel();
    sip_start = new javax.swing.JTextField();
    jLabel36 = new javax.swing.JLabel();
    sip_end = new javax.swing.JTextField();
    jLabel35 = new javax.swing.JLabel();
    sip_name = new javax.swing.JTextField();
    sip_pass = new javax.swing.JPasswordField();
    jPanel3 = new javax.swing.JPanel();
    create_list = new javax.swing.JButton();
    list_name = new javax.swing.JTextField();
    jLabel5 = new javax.swing.JLabel();
    selected_list = new javax.swing.JComboBox();
    reset_list = new javax.swing.JButton();
    delete_list = new javax.swing.JButton();
    export_list = new javax.swing.JButton();
    import_list = new javax.swing.JButton();
    fix_list = new javax.swing.JButton();
    jLabel33 = new javax.swing.JLabel();
    jButton2 = new javax.swing.JButton();
    jPanel11 = new javax.swing.JPanel();
    jScrollPane1 = new javax.swing.JScrollPane();
    listView = new javax.swing.JTable();
    jPanel4 = new javax.swing.JPanel();
    jPanel1 = new javax.swing.JPanel();
    jLabel2 = new javax.swing.JLabel();
    jLabel3 = new javax.swing.JLabel();
    jLabel4 = new javax.swing.JLabel();
    greeting_threshold = new javax.swing.JTextField();
    silence_threshold = new javax.swing.JTextField();
    silence_duration = new javax.swing.JTextField();
    human_vm_detect = new javax.swing.JCheckBox();
    voicemail_hangup = new javax.swing.JCheckBox();
    jPanel7 = new javax.swing.JPanel();
    jLabel13 = new javax.swing.JLabel();
    enable_g729a = new javax.swing.JCheckBox();
    enable_reinvites = new javax.swing.JCheckBox();
    jLabel6 = new javax.swing.JLabel();
    maxRingTime = new javax.swing.JSpinner();
    enable_g711u = new javax.swing.JCheckBox();
    enable_g711a = new javax.swing.JCheckBox();
    number_lines = new javax.swing.JSpinner();
    jLabel24 = new javax.swing.JLabel();
    jLabel23 = new javax.swing.JLabel();
    delay = new javax.swing.JSpinner();
    check_update = new javax.swing.JCheckBox();
    maxAttempts = new javax.swing.JSpinner();
    exit_on_close_window = new javax.swing.JCheckBox();
    jPanel8 = new javax.swing.JPanel();
    xfer_number = new javax.swing.JTextField();
    jLabel7 = new javax.swing.JLabel();
    xfer_digit = new javax.swing.JTextField();
    enable_xfer = new javax.swing.JCheckBox();
    jPanel9 = new javax.swing.JPanel();
    jPanel10 = new javax.swing.JPanel();
    jLabel26 = new javax.swing.JLabel();
    day1 = new javax.swing.JCheckBox();
    start1 = new javax.swing.JTextField();
    jLabel25 = new javax.swing.JLabel();
    end1 = new javax.swing.JTextField();
    day2 = new javax.swing.JCheckBox();
    start2 = new javax.swing.JTextField();
    jLabel27 = new javax.swing.JLabel();
    end2 = new javax.swing.JTextField();
    day3 = new javax.swing.JCheckBox();
    start3 = new javax.swing.JTextField();
    jLabel28 = new javax.swing.JLabel();
    end3 = new javax.swing.JTextField();
    day4 = new javax.swing.JCheckBox();
    start4 = new javax.swing.JTextField();
    jLabel29 = new javax.swing.JLabel();
    end4 = new javax.swing.JTextField();
    end5 = new javax.swing.JTextField();
    jLabel30 = new javax.swing.JLabel();
    start5 = new javax.swing.JTextField();
    day5 = new javax.swing.JCheckBox();
    day6 = new javax.swing.JCheckBox();
    start6 = new javax.swing.JTextField();
    jLabel31 = new javax.swing.JLabel();
    end6 = new javax.swing.JTextField();
    day7 = new javax.swing.JCheckBox();
    start7 = new javax.swing.JTextField();
    jLabel32 = new javax.swing.JLabel();
    end7 = new javax.swing.JTextField();
    jPanel5 = new javax.swing.JPanel();
    enable = new javax.swing.JCheckBox();
    wav_filename = new javax.swing.JTextField();
    select_file = new javax.swing.JButton();
    next_msg = new javax.swing.JButton();
    prev_msg = new javax.swing.JButton();
    title = new javax.swing.JLabel();
    single = new javax.swing.JRadioButton();
    count = new javax.swing.JComboBox();
    multi = new javax.swing.JRadioButton();
    terminate = new javax.swing.JRadioButton();
    gotouser = new javax.swing.JRadioButton();
    goto_1 = new javax.swing.JTextField();
    goto_2 = new javax.swing.JTextField();
    goto_3 = new javax.swing.JTextField();
    goto_4 = new javax.swing.JTextField();
    goto_5 = new javax.swing.JTextField();
    goto_6 = new javax.swing.JTextField();
    goto_7 = new javax.swing.JTextField();
    goto_8 = new javax.swing.JTextField();
    goto_9 = new javax.swing.JTextField();
    jLabel8 = new javax.swing.JLabel();
    jLabel14 = new javax.swing.JLabel();
    jLabel15 = new javax.swing.JLabel();
    jLabel16 = new javax.swing.JLabel();
    jLabel17 = new javax.swing.JLabel();
    jLabel18 = new javax.swing.JLabel();
    jLabel19 = new javax.swing.JLabel();
    jLabel20 = new javax.swing.JLabel();
    jLabel21 = new javax.swing.JLabel();
    cont = new javax.swing.JRadioButton();
    gotoidx = new javax.swing.JRadioButton();
    gotoidxnum = new javax.swing.JTextField();
    jLabel22 = new javax.swing.JLabel();
    msg_idx = new javax.swing.JSlider();
    xfer = new javax.swing.JRadioButton();
    jPanel6 = new javax.swing.JPanel();
    jScrollPane2 = new javax.swing.JScrollPane();
    help = new javax.swing.JTextArea();
    save_setup = new javax.swing.JButton();
    jButton1 = new javax.swing.JButton();

    setTitle("jfBroadcast");
    addWindowListener(new java.awt.event.WindowAdapter() {
      public void windowClosing(java.awt.event.WindowEvent evt) {
        formWindowClosing(evt);
      }
    });

    jLabel1.setText("Broadcast a message to a list of phone numbers or take a survey.");

    start.setText("Start!");
    start.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        startActionPerformed(evt);
      }
    });

    status.setText("Status:OK");

    jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("SIP Details"));

    jLabel9.setText("Username");

    jLabel10.setText("Authorization Username");
    jLabel10.setToolTipText("optional");

    jLabel11.setText("Domain");

    jLabel12.setText("Password");

    jLabel34.setText("SIP Port Range:");
    jLabel34.setToolTipText("Valid range : 1024 thru 65535");

    sip_start.setText("6000");
    sip_start.setToolTipText("1024 - 60000");

    jLabel36.setText("to");

    sip_end.setText("9000");
    sip_end.setToolTipText("1024 - 65535");

    jLabel35.setText("Display Name");
    jLabel35.setToolTipText("optional");

    javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
    jPanel2.setLayout(jPanel2Layout);
    jPanel2Layout.setHorizontalGroup(
      jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel2Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(jPanel2Layout.createSequentialGroup()
            .addComponent(jLabel34)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(sip_start, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel36)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(sip_end, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(0, 465, Short.MAX_VALUE))
          .addGroup(jPanel2Layout.createSequentialGroup()
            .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(jLabel10)
              .addComponent(jLabel9)
              .addComponent(jLabel35)
              .addComponent(jLabel11)
              .addComponent(jLabel12))
            .addGap(25, 25, 25)
            .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
              .addComponent(sip_auth)
              .addComponent(sip_name)
              .addComponent(sip_user)
              .addComponent(sip_host)
              .addComponent(sip_pass, javax.swing.GroupLayout.Alignment.LEADING))))
        .addContainerGap())
    );
    jPanel2Layout.setVerticalGroup(
      jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel2Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel35)
          .addComponent(sip_name, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel9)
          .addComponent(sip_user, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel10)
          .addComponent(sip_auth, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel11)
          .addComponent(sip_host, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel12)
          .addComponent(sip_pass, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addGap(18, 18, 18)
        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(sip_start, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel36)
          .addComponent(sip_end, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel34))
        .addContainerGap(204, Short.MAX_VALUE))
    );

    jTabbedPane1.addTab("SIP", jPanel2);

    jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("Lists"));

    create_list.setText("Create List...");
    create_list.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        create_listActionPerformed(evt);
      }
    });

    list_name.setText("default");

    jLabel5.setText("Select List:");

    selected_list.addItemListener(new java.awt.event.ItemListener() {
      public void itemStateChanged(java.awt.event.ItemEvent evt) {
        selected_listItemStateChanged(evt);
      }
    });

    reset_list.setText("Reset List...");
    reset_list.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        reset_listActionPerformed(evt);
      }
    });

    delete_list.setText("Delete List...");
    delete_list.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        delete_listActionPerformed(evt);
      }
    });

    export_list.setText("Export List...");
    export_list.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        export_listActionPerformed(evt);
      }
    });

    import_list.setText("Import List...");
    import_list.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        import_listActionPerformed(evt);
      }
    });

    fix_list.setText("Fix 'calling' in List...");
    fix_list.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        fix_listActionPerformed(evt);
      }
    });

    jLabel33.setText("Name:");

    jButton2.setText("Fix 'errors' in List...");
    jButton2.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButton2ActionPerformed(evt);
      }
    });

    javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
    jPanel3.setLayout(jPanel3Layout);
    jPanel3Layout.setHorizontalGroup(
      jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel3Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(jPanel3Layout.createSequentialGroup()
            .addComponent(jLabel5)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(selected_list, javax.swing.GroupLayout.PREFERRED_SIZE, 158, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addGroup(jPanel3Layout.createSequentialGroup()
            .addComponent(import_list)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(export_list))
          .addGroup(jPanel3Layout.createSequentialGroup()
            .addComponent(reset_list)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(delete_list))
          .addGroup(jPanel3Layout.createSequentialGroup()
            .addComponent(fix_list)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jButton2))
          .addGroup(jPanel3Layout.createSequentialGroup()
            .addComponent(jLabel33)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(list_name, javax.swing.GroupLayout.PREFERRED_SIZE, 175, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(create_list)))
        .addContainerGap(371, Short.MAX_VALUE))
    );
    jPanel3Layout.setVerticalGroup(
      jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel3Layout.createSequentialGroup()
        .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(list_name, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(create_list)
          .addComponent(jLabel33))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel5)
          .addComponent(selected_list, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(import_list)
          .addComponent(export_list))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(reset_list)
          .addComponent(delete_list))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(fix_list)
          .addComponent(jButton2))
        .addContainerGap(241, Short.MAX_VALUE))
    );

    jTabbedPane1.addTab("Lists", jPanel3);

    listView.setModel(new javax.swing.table.DefaultTableModel(
      new Object [][] {

      },
      new String [] {
        "Number", "Status", "Attempts", "Survey", "Timestamp"
      }
    ) {
      Class[] types = new Class [] {
        java.lang.String.class, java.lang.String.class, java.lang.Integer.class, java.lang.String.class, java.lang.String.class
      };
      boolean[] canEdit = new boolean [] {
        false, false, false, false, false
      };

      public Class getColumnClass(int columnIndex) {
        return types [columnIndex];
      }

      public boolean isCellEditable(int rowIndex, int columnIndex) {
        return canEdit [columnIndex];
      }
    });
    listView.setEnabled(false);
    jScrollPane1.setViewportView(listView);

    javax.swing.GroupLayout jPanel11Layout = new javax.swing.GroupLayout(jPanel11);
    jPanel11.setLayout(jPanel11Layout);
    jPanel11Layout.setHorizontalGroup(
      jPanel11Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel11Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 686, Short.MAX_VALUE)
        .addContainerGap())
    );
    jPanel11Layout.setVerticalGroup(
      jPanel11Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel11Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 378, Short.MAX_VALUE)
        .addContainerGap())
    );

    jTabbedPane1.addTab("Active List", jPanel11);

    jPanel4.setBorder(javax.swing.BorderFactory.createTitledBorder("Options"));

    jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Human/Voicemail Detection Options"));

    jLabel2.setText("Greeting Detection Threshold:");
    jLabel2.setToolTipText("100-32000 (default = 1000)");

    jLabel3.setText("Silence Detection Threshold:");
    jLabel3.setToolTipText("100-32000 (default = 1000)");

    jLabel4.setText("Silence Duration (ms):");
    jLabel4.setToolTipText("100-5000 (default = 2000)");

    greeting_threshold.setText("1000");
    greeting_threshold.setToolTipText("100-32000 (default = 1000)");

    silence_threshold.setText("1000");
    silence_threshold.setToolTipText("100-32000 (default = 1000)");

    silence_duration.setText("2000");
    silence_duration.setToolTipText("100-5000 (default = 1000)");

    human_vm_detect.setSelected(true);
    human_vm_detect.setText("Enable human/voicemail detection");
    human_vm_detect.setToolTipText("Start messages after human/voicemail greeting.");
    human_vm_detect.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        human_vm_detectActionPerformed(evt);
      }
    });

    voicemail_hangup.setText("Voicemail hangup");
    voicemail_hangup.setToolTipText("Hangup call if voicemail is detected.");

    javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
    jPanel1.setLayout(jPanel1Layout);
    jPanel1Layout.setHorizontalGroup(
      jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel1Layout.createSequentialGroup()
        .addComponent(human_vm_detect)
        .addGap(0, 0, Short.MAX_VALUE))
      .addGroup(jPanel1Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
          .addComponent(voicemail_hangup, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
          .addGroup(jPanel1Layout.createSequentialGroup()
            .addComponent(jLabel2)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(greeting_threshold, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addGroup(jPanel1Layout.createSequentialGroup()
            .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(jLabel3)
              .addComponent(jLabel4))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
            .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(silence_threshold)
              .addComponent(silence_duration, javax.swing.GroupLayout.PREFERRED_SIZE, 122, javax.swing.GroupLayout.PREFERRED_SIZE))))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );
    jPanel1Layout.setVerticalGroup(
      jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel1Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(human_vm_detect)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel2)
          .addComponent(greeting_threshold, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel3)
          .addComponent(silence_threshold, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addGap(7, 7, 7)
        .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel4)
          .addComponent(silence_duration, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
        .addComponent(voicemail_hangup)
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );

    jPanel7.setBorder(javax.swing.BorderFactory.createTitledBorder("General Options"));

    jLabel13.setText("Delay (ms) :");
    jLabel13.setToolTipText("100-10000");

    enable_g729a.setSelected(true);
    enable_g729a.setText("Enable g729a codec");

    enable_reinvites.setSelected(true);
    enable_reinvites.setText("Enable reinvites");

    jLabel6.setText("Number of Lines:");
    jLabel6.setToolTipText("Number of concurrent calls.");

    maxRingTime.addChangeListener(new javax.swing.event.ChangeListener() {
      public void stateChanged(javax.swing.event.ChangeEvent evt) {
        maxRingTimeStateChanged(evt);
      }
    });

    enable_g711u.setSelected(true);
    enable_g711u.setText("Enable g711u codec");

    enable_g711a.setText("Enable g711a codec");

    number_lines.setValue(4);
    number_lines.addChangeListener(new javax.swing.event.ChangeListener() {
      public void stateChanged(javax.swing.event.ChangeEvent evt) {
        number_linesStateChanged(evt);
      }
    });

    jLabel24.setText("Max Attempts:");
    jLabel24.setToolTipText("Max attempts to call each number.");

    jLabel23.setText("Max Ring Time:");
    jLabel23.setToolTipText("Max ring time in seconds.");

    delay.setToolTipText("100-10000");
    delay.addChangeListener(new javax.swing.event.ChangeListener() {
      public void stateChanged(javax.swing.event.ChangeEvent evt) {
        delayStateChanged(evt);
      }
    });

    check_update.setSelected(true);
    check_update.setText("Check for update on startup");

    maxAttempts.addChangeListener(new javax.swing.event.ChangeListener() {
      public void stateChanged(javax.swing.event.ChangeEvent evt) {
        maxAttemptsStateChanged(evt);
      }
    });

    exit_on_close_window.setText("Close app when closing window");

    javax.swing.GroupLayout jPanel7Layout = new javax.swing.GroupLayout(jPanel7);
    jPanel7.setLayout(jPanel7Layout);
    jPanel7Layout.setHorizontalGroup(
      jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel7Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(exit_on_close_window)
          .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel7Layout.createSequentialGroup()
              .addComponent(jLabel24)
              .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
              .addComponent(maxAttempts))
            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel7Layout.createSequentialGroup()
              .addComponent(jLabel23)
              .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
              .addComponent(maxRingTime))
            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel7Layout.createSequentialGroup()
              .addComponent(jLabel13)
              .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
              .addComponent(delay))
            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel7Layout.createSequentialGroup()
              .addComponent(jLabel6)
              .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
              .addComponent(number_lines, javax.swing.GroupLayout.PREFERRED_SIZE, 115, javax.swing.GroupLayout.PREFERRED_SIZE)))
          .addComponent(enable_g711a)
          .addComponent(enable_g711u)
          .addComponent(enable_g729a)
          .addComponent(check_update)
          .addComponent(enable_reinvites))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );
    jPanel7Layout.setVerticalGroup(
      jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel7Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel6)
          .addComponent(number_lines, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel13)
          .addComponent(delay, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addGap(7, 7, 7)
        .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel23)
          .addComponent(maxRingTime, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel24)
          .addComponent(maxAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
        .addComponent(enable_g729a)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(enable_g711u)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(enable_g711a)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(enable_reinvites)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(check_update)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(exit_on_close_window)
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );

    jPanel8.setBorder(javax.swing.BorderFactory.createTitledBorder("Transfer Option"));

    jLabel7.setText("Transfer destination =");

    xfer_digit.setText("0");

    enable_xfer.setText("Enable transfer : Digit =");

    javax.swing.GroupLayout jPanel8Layout = new javax.swing.GroupLayout(jPanel8);
    jPanel8.setLayout(jPanel8Layout);
    jPanel8Layout.setHorizontalGroup(
      jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel8Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(enable_xfer)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(xfer_digit, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jLabel7)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(xfer_number)
        .addContainerGap())
    );
    jPanel8Layout.setVerticalGroup(
      jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel8Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(enable_xfer)
          .addComponent(xfer_digit, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(xfer_number, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel7))
        .addContainerGap())
    );

    javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
    jPanel4.setLayout(jPanel4Layout);
    jPanel4Layout.setHorizontalGroup(
      jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel4Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(jPanel8, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
          .addGroup(jPanel4Layout.createSequentialGroup()
            .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(0, 136, Short.MAX_VALUE)))
        .addContainerGap())
    );
    jPanel4Layout.setVerticalGroup(
      jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel4Layout.createSequentialGroup()
        .addGap(10, 10, 10)
        .addComponent(jPanel8, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );

    jTabbedPane1.addTab("Options", jPanel4);

    jPanel10.setBorder(javax.swing.BorderFactory.createTitledBorder("Calling Times"));

    jLabel26.setText("List all times in 24HR format.");

    day1.setText("Sunday");

    start1.setText("10:00");

    jLabel25.setText("to");

    end1.setText("17:00");

    day2.setSelected(true);
    day2.setText("Monday");

    start2.setText("10:00");

    jLabel27.setText("to");

    end2.setText("17:00");

    day3.setSelected(true);
    day3.setText("Tuesday");

    start3.setText("10:00");

    jLabel28.setText("to");

    end3.setText("17:00");

    day4.setSelected(true);
    day4.setText("Wednesday");

    start4.setText("10:00");

    jLabel29.setText("to");

    end4.setText("17:00");

    end5.setText("17:00");

    jLabel30.setText("to");

    start5.setText("10:00");

    day5.setSelected(true);
    day5.setText("Thursday");

    day6.setSelected(true);
    day6.setText("Friday");

    start6.setText("10:00");

    jLabel31.setText("to");

    end6.setText("17:00");

    day7.setText("Saturday");

    start7.setText("10:00");

    jLabel32.setText("to");

    end7.setText("17:00");

    javax.swing.GroupLayout jPanel10Layout = new javax.swing.GroupLayout(jPanel10);
    jPanel10.setLayout(jPanel10Layout);
    jPanel10Layout.setHorizontalGroup(
      jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel10Layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(jPanel10Layout.createSequentialGroup()
            .addComponent(day1)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(start1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel25)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(end1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addGroup(jPanel10Layout.createSequentialGroup()
            .addComponent(jLabel26)
            .addGap(0, 0, Short.MAX_VALUE))
          .addGroup(jPanel10Layout.createSequentialGroup()
            .addComponent(day2)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(start2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel27)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(end2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addGroup(jPanel10Layout.createSequentialGroup()
            .addComponent(day3)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(start3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel28)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(end3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addGroup(jPanel10Layout.createSequentialGroup()
            .addComponent(day4)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(start4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel29)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(end4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addGroup(jPanel10Layout.createSequentialGroup()
            .addComponent(day5)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(start5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel30)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(end5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addGroup(jPanel10Layout.createSequentialGroup()
            .addComponent(day6)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(start6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel31)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(end6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addGroup(jPanel10Layout.createSequentialGroup()
            .addComponent(day7)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(start7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel32)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(end7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
        .addContainerGap())
    );
    jPanel10Layout.setVerticalGroup(
      jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel10Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(jLabel26)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
        .addGroup(jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(day1)
          .addComponent(start1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel25)
          .addComponent(end1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(day2)
          .addComponent(start2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel27)
          .addComponent(end2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(day3)
          .addComponent(start3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel28)
          .addComponent(end3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(day4)
          .addComponent(start4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel29)
          .addComponent(end4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(day5)
          .addComponent(start5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel30)
          .addComponent(end5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(day6)
          .addComponent(start6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel31)
          .addComponent(end6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel10Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(day7)
          .addComponent(start7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel32)
          .addComponent(end7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );

    javax.swing.GroupLayout jPanel9Layout = new javax.swing.GroupLayout(jPanel9);
    jPanel9.setLayout(jPanel9Layout);
    jPanel9Layout.setHorizontalGroup(
      jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel9Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(jPanel10, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addContainerGap(493, Short.MAX_VALUE))
    );
    jPanel9Layout.setVerticalGroup(
      jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel9Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(jPanel10, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addContainerGap(143, Short.MAX_VALUE))
    );

    jTabbedPane1.addTab("Times", jPanel9);

    jPanel5.setBorder(javax.swing.BorderFactory.createTitledBorder("Messages / Questions"));

    enable.setText("Enable : WAV=");

    wav_filename.setText("filename.wav");

    select_file.setText("...");
    select_file.setToolTipText("Select File...");
    select_file.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        select_fileActionPerformed(evt);
      }
    });

    next_msg.setText(">");
    next_msg.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        next_msgActionPerformed(evt);
      }
    });

    prev_msg.setText("<");
    prev_msg.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        prev_msgActionPerformed(evt);
      }
    });

    title.setText("#1/99");

    single.setText("Single digit response : allowed responses =");

    count.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "1", "1-2", "1-3", "1-4", "1-5", "1-6", "1-7", "1-8", "1-9" }));

    multi.setText("Long response (user ends input with # or after 5 sec pause) (continues to next message)");

    terminate.setText("Terminate call after this message");

    gotouser.setText("Goto message from user input (1-99)");
    gotouser.setToolTipText("Goto question based on digit pressed.");

    goto_1.setText("0");
    goto_1.setToolTipText("1");

    goto_2.setText("0");
    goto_2.setToolTipText("2");

    goto_3.setText("0");
    goto_3.setToolTipText("3");

    goto_4.setText("0");
    goto_4.setToolTipText("4");

    goto_5.setText("0");
    goto_5.setToolTipText("5");

    goto_6.setText("0");
    goto_6.setToolTipText("6");

    goto_7.setText("0");
    goto_7.setToolTipText("7");

    goto_8.setText("0");
    goto_8.setToolTipText("8");

    goto_9.setText("0");
    goto_9.setToolTipText("9");

    jLabel8.setText("1:");

    jLabel14.setText("3:");

    jLabel15.setText("2:");

    jLabel16.setText("4:");

    jLabel17.setText("5:");

    jLabel18.setText("7:");

    jLabel19.setText("6:");

    jLabel20.setText("9:");

    jLabel21.setText("8:");

    cont.setSelected(true);
    cont.setText("Continue to next message");

    gotoidx.setText("Goto message after this message (1-99)");

    gotoidxnum.setText("0");

    jLabel22.setText("(continues to next message)");

    msg_idx.setMaximum(98);
    msg_idx.setValue(0);
    msg_idx.addChangeListener(new javax.swing.event.ChangeListener() {
      public void stateChanged(javax.swing.event.ChangeEvent evt) {
        msg_idxStateChanged(evt);
      }
    });

    xfer.setText("Transfer call after message");

    javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5);
    jPanel5.setLayout(jPanel5Layout);
    jPanel5Layout.setHorizontalGroup(
      jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel5Layout.createSequentialGroup()
        .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel5Layout.createSequentialGroup()
            .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addGroup(jPanel5Layout.createSequentialGroup()
                .addComponent(enable)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(wav_filename, javax.swing.GroupLayout.DEFAULT_SIZE, 391, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED))
              .addGroup(jPanel5Layout.createSequentialGroup()
                .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                  .addComponent(cont)
                  .addComponent(terminate))
                .addGap(249, 249, 249)))
            .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
              .addComponent(msg_idx, 0, 0, Short.MAX_VALUE)
              .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel5Layout.createSequentialGroup()
                .addComponent(select_file)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(title, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(prev_msg)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(next_msg))))
          .addGroup(jPanel5Layout.createSequentialGroup()
            .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(multi)
              .addGroup(jPanel5Layout.createSequentialGroup()
                .addComponent(single)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(count, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(jLabel22))
              .addGroup(jPanel5Layout.createSequentialGroup()
                .addComponent(gotoidx)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(gotoidxnum, javax.swing.GroupLayout.PREFERRED_SIZE, 47, javax.swing.GroupLayout.PREFERRED_SIZE))
              .addGroup(jPanel5Layout.createSequentialGroup()
                .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                  .addComponent(gotouser, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                  .addComponent(xfer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel8)
                .addGap(2, 2, 2)
                .addComponent(goto_1, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel15)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(goto_2, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel14)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(goto_3, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel16)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(goto_4, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel17)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(goto_5, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel19)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(goto_6, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel18)
                .addGap(6, 6, 6)
                .addComponent(goto_7, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel21)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(goto_8, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel20)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(goto_9, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)))
            .addGap(0, 0, Short.MAX_VALUE)))
        .addContainerGap())
    );
    jPanel5Layout.setVerticalGroup(
      jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel5Layout.createSequentialGroup()
        .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(enable)
          .addComponent(wav_filename, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(next_msg)
          .addComponent(prev_msg)
          .addComponent(title)
          .addComponent(select_file))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(jPanel5Layout.createSequentialGroup()
            .addComponent(cont)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(terminate)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
              .addComponent(single)
              .addComponent(count, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
              .addComponent(jLabel22)))
          .addComponent(msg_idx, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(multi)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(gotoidx)
          .addComponent(gotoidxnum, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(gotouser)
          .addComponent(goto_1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel8)
          .addComponent(jLabel15)
          .addComponent(goto_2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(goto_3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(goto_4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel14)
          .addComponent(jLabel16)
          .addComponent(jLabel17)
          .addComponent(goto_5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(goto_6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(goto_7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(goto_8, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel18)
          .addComponent(jLabel19)
          .addComponent(jLabel21)
          .addComponent(jLabel20)
          .addComponent(goto_9, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
        .addComponent(xfer)
        .addContainerGap(177, Short.MAX_VALUE))
    );

    jTabbedPane1.addTab("Messages / Questions", jPanel5);

    help.setEditable(false);
    help.setColumns(20);
    help.setRows(5);
    jScrollPane2.setViewportView(help);

    javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6);
    jPanel6.setLayout(jPanel6Layout);
    jPanel6Layout.setHorizontalGroup(
      jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 706, Short.MAX_VALUE)
    );
    jPanel6Layout.setVerticalGroup(
      jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
    );

    jTabbedPane1.addTab("Help", jPanel6);

    save_setup.setText("Save Setup");
    save_setup.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        save_setupActionPerformed(evt);
      }
    });

    jButton1.setText("Donate");
    jButton1.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButton1ActionPerformed(evt);
      }
    });

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(jTabbedPane1)
          .addGroup(layout.createSequentialGroup()
            .addComponent(jLabel1)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(jButton1)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(save_setup))
          .addGroup(layout.createSequentialGroup()
            .addComponent(start)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(status, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
        .addContainerGap())
    );
    layout.setVerticalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel1)
          .addComponent(save_setup)
          .addComponent(jButton1))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jTabbedPane1)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(start)
          .addComponent(status))
        .addContainerGap())
    );

    pack();
  }// </editor-fold>//GEN-END:initComponents

    private void startActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startActionPerformed
      if (state == states.CALLING) {
        stopCalling();
        return;
      }
      if (state == states.STOPPING) {
        if (JFAWT.showConfirm("Warning", "Are you sure you want to force stop?")) {
          forceStopCalling();
        }
        return;
      }
      callingThread = new Thread() {
        @Override
        public void run() {
          startCalling();
        }
      };
      callingThread.start();
    }//GEN-LAST:event_startActionPerformed

    private void save_setupActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_save_setupActionPerformed
      saveSetup();
    }//GEN-LAST:event_save_setupActionPerformed

  private void msg_idxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_msg_idxStateChanged
    gotoQuestion(msg_idx.getValue());
  }//GEN-LAST:event_msg_idxStateChanged

  private void prev_msgActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prev_msgActionPerformed
    prevQuestion();
  }//GEN-LAST:event_prev_msgActionPerformed

  private void next_msgActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_next_msgActionPerformed
    nextQuestion();
  }//GEN-LAST:event_next_msgActionPerformed

  private void select_fileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_select_fileActionPerformed
    selectFile();
  }//GEN-LAST:event_select_fileActionPerformed

  private void maxAttemptsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_maxAttemptsStateChanged
    validateOptions();
  }//GEN-LAST:event_maxAttemptsStateChanged

  private void maxRingTimeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_maxRingTimeStateChanged
    validateOptions();
  }//GEN-LAST:event_maxRingTimeStateChanged

  private void number_linesStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_number_linesStateChanged
    validateOptions();
  }//GEN-LAST:event_number_linesStateChanged

  private void delayStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_delayStateChanged
    validateOptions();
  }//GEN-LAST:event_delayStateChanged

  private void fix_listActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fix_listActionPerformed
    fixList(false, false);
  }//GEN-LAST:event_fix_listActionPerformed

  private void import_listActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_import_listActionPerformed
    importList();
  }//GEN-LAST:event_import_listActionPerformed

  private void export_listActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_export_listActionPerformed
    exportList();
  }//GEN-LAST:event_export_listActionPerformed

  private void delete_listActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_delete_listActionPerformed
    deleteList();
  }//GEN-LAST:event_delete_listActionPerformed

  private void reset_listActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_reset_listActionPerformed
    resetList();
  }//GEN-LAST:event_reset_listActionPerformed

  private void selected_listItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_selected_listItemStateChanged
    updateList();
  }//GEN-LAST:event_selected_listItemStateChanged

  private void create_listActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_create_listActionPerformed
    createList();
  }//GEN-LAST:event_create_listActionPerformed

  private void human_vm_detectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_human_vm_detectActionPerformed
    setOptsState();
  }//GEN-LAST:event_human_vm_detectActionPerformed

  private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
    JFAWT.donate();
  }//GEN-LAST:event_jButton1ActionPerformed

  private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
    if (settings.exit_on_close_window) {
      if (lockFile != null) {
        lockFile.unlock();
      }
      System.exit(0);
    } else {
      icon.displayMessage(null, "jfBroadcast is still active", TrayIcon.MessageType.NONE);
    }
  }//GEN-LAST:event_formWindowClosing

  private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
    fixList(false, true);
  }//GEN-LAST:event_jButton2ActionPerformed

  /**
   * @param args the command line arguments
   */
  public static void main(String args[]) {
    Broadcast.args = args;
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new Broadcast().setVisible(true);
      }
    });
  }
  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.ButtonGroup buttonGroup1;
  private javax.swing.JCheckBox check_update;
  private javax.swing.JRadioButton cont;
  private javax.swing.JComboBox count;
  private javax.swing.JButton create_list;
  private javax.swing.JCheckBox day1;
  private javax.swing.JCheckBox day2;
  private javax.swing.JCheckBox day3;
  private javax.swing.JCheckBox day4;
  private javax.swing.JCheckBox day5;
  private javax.swing.JCheckBox day6;
  private javax.swing.JCheckBox day7;
  private javax.swing.JSpinner delay;
  private javax.swing.JButton delete_list;
  private javax.swing.JCheckBox enable;
  private javax.swing.JCheckBox enable_g711a;
  private javax.swing.JCheckBox enable_g711u;
  private javax.swing.JCheckBox enable_g729a;
  private javax.swing.JCheckBox enable_reinvites;
  private javax.swing.JCheckBox enable_xfer;
  private javax.swing.JTextField end1;
  private javax.swing.JTextField end2;
  private javax.swing.JTextField end3;
  private javax.swing.JTextField end4;
  private javax.swing.JTextField end5;
  private javax.swing.JTextField end6;
  private javax.swing.JTextField end7;
  private javax.swing.JCheckBox exit_on_close_window;
  private javax.swing.JButton export_list;
  private javax.swing.JButton fix_list;
  private javax.swing.JTextField goto_1;
  private javax.swing.JTextField goto_2;
  private javax.swing.JTextField goto_3;
  private javax.swing.JTextField goto_4;
  private javax.swing.JTextField goto_5;
  private javax.swing.JTextField goto_6;
  private javax.swing.JTextField goto_7;
  private javax.swing.JTextField goto_8;
  private javax.swing.JTextField goto_9;
  private javax.swing.JRadioButton gotoidx;
  private javax.swing.JTextField gotoidxnum;
  private javax.swing.JRadioButton gotouser;
  private javax.swing.JTextField greeting_threshold;
  private javax.swing.JTextArea help;
  private javax.swing.JCheckBox human_vm_detect;
  private javax.swing.JButton import_list;
  private javax.swing.JButton jButton1;
  private javax.swing.JButton jButton2;
  private javax.swing.JLabel jLabel1;
  private javax.swing.JLabel jLabel10;
  private javax.swing.JLabel jLabel11;
  private javax.swing.JLabel jLabel12;
  private javax.swing.JLabel jLabel13;
  private javax.swing.JLabel jLabel14;
  private javax.swing.JLabel jLabel15;
  private javax.swing.JLabel jLabel16;
  private javax.swing.JLabel jLabel17;
  private javax.swing.JLabel jLabel18;
  private javax.swing.JLabel jLabel19;
  private javax.swing.JLabel jLabel2;
  private javax.swing.JLabel jLabel20;
  private javax.swing.JLabel jLabel21;
  private javax.swing.JLabel jLabel22;
  private javax.swing.JLabel jLabel23;
  private javax.swing.JLabel jLabel24;
  private javax.swing.JLabel jLabel25;
  private javax.swing.JLabel jLabel26;
  private javax.swing.JLabel jLabel27;
  private javax.swing.JLabel jLabel28;
  private javax.swing.JLabel jLabel29;
  private javax.swing.JLabel jLabel3;
  private javax.swing.JLabel jLabel30;
  private javax.swing.JLabel jLabel31;
  private javax.swing.JLabel jLabel32;
  private javax.swing.JLabel jLabel33;
  private javax.swing.JLabel jLabel34;
  private javax.swing.JLabel jLabel35;
  private javax.swing.JLabel jLabel36;
  private javax.swing.JLabel jLabel4;
  private javax.swing.JLabel jLabel5;
  private javax.swing.JLabel jLabel6;
  private javax.swing.JLabel jLabel7;
  private javax.swing.JLabel jLabel8;
  private javax.swing.JLabel jLabel9;
  private javax.swing.JPanel jPanel1;
  private javax.swing.JPanel jPanel10;
  private javax.swing.JPanel jPanel11;
  private javax.swing.JPanel jPanel2;
  private javax.swing.JPanel jPanel3;
  private javax.swing.JPanel jPanel4;
  private javax.swing.JPanel jPanel5;
  private javax.swing.JPanel jPanel6;
  private javax.swing.JPanel jPanel7;
  private javax.swing.JPanel jPanel8;
  private javax.swing.JPanel jPanel9;
  private javax.swing.JScrollPane jScrollPane1;
  private javax.swing.JScrollPane jScrollPane2;
  private javax.swing.JTabbedPane jTabbedPane1;
  private javax.swing.JTable listView;
  private javax.swing.JTextField list_name;
  private javax.swing.JSpinner maxAttempts;
  private javax.swing.JSpinner maxRingTime;
  private javax.swing.JSlider msg_idx;
  private javax.swing.JRadioButton multi;
  private javax.swing.JButton next_msg;
  private javax.swing.JSpinner number_lines;
  private javax.swing.JButton prev_msg;
  private javax.swing.JButton reset_list;
  private javax.swing.JButton save_setup;
  private javax.swing.JButton select_file;
  private javax.swing.JComboBox selected_list;
  private javax.swing.JTextField silence_duration;
  private javax.swing.JTextField silence_threshold;
  private javax.swing.JRadioButton single;
  private javax.swing.JTextField sip_auth;
  private javax.swing.JTextField sip_end;
  private javax.swing.JTextField sip_host;
  private javax.swing.JTextField sip_name;
  private javax.swing.JPasswordField sip_pass;
  private javax.swing.JTextField sip_start;
  private javax.swing.JTextField sip_user;
  private javax.swing.JButton start;
  private javax.swing.JTextField start1;
  private javax.swing.JTextField start2;
  private javax.swing.JTextField start3;
  private javax.swing.JTextField start4;
  private javax.swing.JTextField start5;
  private javax.swing.JTextField start6;
  private javax.swing.JTextField start7;
  private javax.swing.JLabel status;
  private javax.swing.JRadioButton terminate;
  private javax.swing.JLabel title;
  private javax.swing.JCheckBox voicemail_hangup;
  private javax.swing.JTextField wav_filename;
  private javax.swing.JRadioButton xfer;
  private javax.swing.JTextField xfer_digit;
  private javax.swing.JTextField xfer_number;
  // End of variables declaration//GEN-END:variables

  //global data
  enum states { STOPPED, CALLING, STOPPING};
  states state = states.STOPPED;
  Thread callingThread;
  int lineCount;
  SIPClient sip;
  Lines lines[];
  boolean registered;
  short silence[] = new short[160];
  short tmp[] = new short[160];
  Wav wav[];
  SQL psql;  //persistant sql connection
  String dbname = "broadcast";
  String listid;
  volatile boolean processing = false;
  int skipCount;
  int maxquietcount;
  static String args[];
  static class update {
    public String number, status, survey;
    public update(String number, String status, String survey) {
      this.number = number;
      this.status = status;
      this.survey = survey;
    }
  }
  Vector<update> updateList = new Vector<update>();
  final static int NUMQUESTIONS = 99;
  //actions
  final static int A_CONT = 0;
  final static int A_TERMINATE = 4;
  final static int A_SINGLE = 1;
  final static int A_MULTI = 2;
  final static int A_GOTOUSER = 3;
  final static int A_GOTOIDX = 5;
  final static int A_XFER = 6;

  public void createDatabase() {
    if (new File(dbPath + "/" + databaseName + "/service.properties").exists()) return;  //already exists
    JFTask task = new JFTask() {
      public boolean work() {
        this.setTitle("Creating database...");
        this.setLabel("Creating database...");
        this.setProgress(25);
        SQL sql = new SQL();
        if (!sql.connect("jdbc:derby:" + databaseName + ";" + "create=true")) {
          this.setLabel("Database create failed : SQL connection failed");
          return false;
        }
        this.setProgress(50);
        if (!sql.execute("create table lists (id int GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1) primary key, name varchar(37) not null unique)")) {
          this.setLabel("Database create failed : Create Table failed (already created?)");
          return false;
        }
        this.setProgress(75);
        if (!sql.execute("create table listdata (id int not null, number varchar(32) not null, status varchar(32) default 'new' not null, attempts int default 0 not null, survey varchar(64), called timestamp, unique(number, id))")) {
          this.setLabel("Database create failed : Create Table failed (already created?)");
          return false;
        }
        this.setProgress(100);
        this.setLabel("Database created succesfully");
        return true;
      }
    };
    ProgressDialog pd = new ProgressDialog(this, true, task);
    pd.setVisible(true);
  }

  public void setStatus(String str) {
    status.setText("Status:" + str);
  }

  public String safe(String str) {
    char strin[] = str.toCharArray();
    char strout[] = new char[str.length()];
    for(int a=0;a<strin.length;a++) {
      if ((Character.toLowerCase(strin[a]) >= 'a') && (Character.toLowerCase(strin[a]) <= 'z')) {
        strout[a] = strin[a];
        continue;
      }
      if ((strin[a] >= '0') && (strin[a] <= '9')) {
        strout[a] = strin[a];
        continue;
      }
      strout[a] = '_';
    }
    return new String(strout);
  }

  public String sip_pass_getText() {
    return new String(sip_pass.getPassword());
  }

  public void createList() {
    String safe = safe(list_name.getText());
    if (safe.length() == 0) return;
    SQL sql = new SQL();
    if (!sql.connect("jdbc:derby:" + databaseName)) {
      setStatus("SQL connection failed");
      return;
    }
    if (!sql.execute("insert into lists values (default, '" + safe + "')")) {
      setStatus("Insert failed (already created?)");
      return;
    }
    setStatus("OK:CreateTable:" + safe);
    updateAvailableLists();
  }

  public void deleteList() {
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return;
    if (JOptionPane.showConfirmDialog(null, "Delete List:" + sel + "?", "Sure?", JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) return;
    SQL sql = new SQL();
    if (!sql.connect("jdbc:derby:" + databaseName)) {
      setStatus("SQL connection failed");
      return;
    }
    if (!sql.execute("delete from listdata where id=(select id from lists where name='"+sel+"')")) {
      setStatus("delete from lists failed(1)");
      return;
    }
    if (!sql.execute("delete from lists where name='"+sel+"'")) {
      setStatus("delete from lists failed(2)");
      return;
    }
    setStatus("OK:DeleteList:"+sel);
    updateAvailableLists();
    updateList();
  }

  public void resetList() {
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return;
    if (JOptionPane.showConfirmDialog(null, "Reset List:" + sel + "?", "Sure?", JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) return;
    SQL sql = new SQL();
    if (!sql.connect("jdbc:derby:" + databaseName)) {
      setStatus("SQL connection failed");
      return;
    }
    if (!sql.execute("update listdata set status='new', attempts = 0, survey = '', called = null where id=(select id from lists where name='" + sel + "')")) {
      setStatus("update failed");
      return;
    }
    setStatus("OK:ResetList:"+sel);
    updateList();
  }

  public void fixList(boolean quite, boolean errors) {
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return;
    if (!quite) {
      String msg;
      if (errors) {
        msg = "Fix 'errors' in List:" + sel + "?";
      } else {
        msg = "Fix 'calling' in List:" + sel + "?";
      }
      if (JOptionPane.showConfirmDialog(null, msg, "Sure?", JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) return;
    }
    SQL sql = new SQL();
    if (!sql.connect("jdbc:derby:" + databaseName)) {
      setStatus("SQL connection failed");
      return;
    }
    String query;
    if (errors) {
      query = "update listdata set status='new', attempts = 0, survey = '', called = null where status like 'err_%' and id=(select id from lists where name='" + sel + "')";
    } else {
      query = "update listdata set status='new', attempts = 0, survey = '', called = null where status='calling' and id=(select id from lists where name='" + sel + "')";
    }
    if (!sql.execute(query)) {
      setStatus("update failed");
      return;
    }
    if (!quite) {
      setStatus("OK:FixList:"+sel);
    }
    updateList();
  }

  public final void updateAvailableLists() {
    File file = new File(dbPath + "/" + databaseName);
    if (!file.exists()) return;  //not ready
    String data[][];
    SQL sql = new SQL();
    if (!sql.connect("jdbc:derby:" + databaseName)) {
//      setStatus("SQL connection failed");
      return;
    }
    data = sql.select("select name from lists");
    if (data == null) {
//      setStatus("select failed");
      return;
    }
    int yc = data.length;
    if (yc == 0) {
      //???
    }
    ArrayList<String> al = new ArrayList<String>();
    String e;
    for(int y=0;y<yc;y++) {
      e = data[y][0];
      al.add(e);
    }
    selected_list.setModel(new DefaultComboBoxModel(al.toArray()));
//    selectedList.setSelectedIndex(0);
    updateList();
  }

  private String getid(SQL sql, String sel) {
    String data[][];
    data = sql.select("select id from lists where name='" + sel +"'");
    if (data == null) {
//      setStatus("select failed");
      return null;
    }
    int yc = data.length;
    if (yc != 1) return null;
    return data[0][0];
  }

  public void importList() {
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return;
    JFileChooser chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getCurrentPath()));
    if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return;
    String num;
    SQL sql = new SQL();
    if (!sql.connect("jdbc:derby:" + databaseName)) {
      setStatus("SQL connection failed");
      return;
    }
    int line = 1;
    int errcnt = 0;
    int okcnt = 0;
    ArrayList<String> numbers = null;
    int duptag = 1;
    if (allowdups) {
      numbers = new ArrayList<String>();
      int cnt = listView.getRowCount();
      for(int a=0;a<cnt;a++) {
        numbers.add((String)listView.getValueAt(a, 0));
      }
    }
    try {
      String id = getid(sql, sel);
      if (id == null) throw new Exception("unable to find list");
      BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(chooser.getSelectedFile().toString())));
      do {
        num = reader.readLine();
        if (num == null) break;
        int idx2 = num.indexOf(",");
        if (idx2 != -1) {
          num = num.substring(0, idx2);
        }
        num = num.trim();
        if (allowdups) {
          //check if this number already exists and add a tag to it
          if (numbers.contains(num)) {
            num += "," + duptag++;
          }
          numbers.add(num);
        }
        if (!sql.execute("insert into listdata (id, number) values (" + id + ",'" + num + "')")) {
          errcnt++;
        } else {
          okcnt++;
        }
        line++;
      } while (true);
      reader.close();
    } catch (Exception e) {
      setStatus(e.toString());
      updateList();
      return;
    }
    setStatus("OK : File loaded : Inserted = " + okcnt + " : Errors = " + errcnt);
    updateList();
  }

  public void exportList() {
    String data[][];
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return;
    JFileChooser chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getCurrentPath()));
    if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return;
    String num;
    SQL sql = new SQL();
    if (!sql.connect("jdbc:derby:" + databaseName)) {
      setStatus("SQL connection failed");
      return;
    }
    data = sql.select("select number,status,attempts,survey,called from listdata where id=(select id from lists where name='" + sel + "')");
    if (data == null) {
      setStatus("select failed (1)");
      return;
    }
    try {
      FileOutputStream fos = new FileOutputStream(chooser.getSelectedFile().toString());
      for(int a=0;a<data.length;a++) {
        String timestamp = data[a][4];
        String date = "null", time = "null";
        if (timestamp != null && timestamp.length() > 10) {
          date = timestamp.substring(0, 10);
          time = timestamp.substring(11);
        }
        fos.write((data[a][0] + "," + data[a][1] + "," + data[a][2] + "," + data[a][3] + "," + date + "," + time + "\r\n").getBytes());
      }
      fos.close();
    } catch (Exception e) {
      setStatus(e.toString());
      return;
    }
    setStatus("OK : List exported");
  }

  private String cols[] = new String[] {"Number","Status","Attempts","Survey","Timestamp"};

  public void updateList() {
    String data[][];
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) {
      listView.setModel(new DefaultTableModel(new String[][] {}, cols));
      return;
    }
    SQL sql = new SQL();
    if (!sql.connect("jdbc:derby:" + databaseName)) {
//      setStatus("SQL connection failed");
      return;
    }
    data = sql.select("select number, status, attempts, survey, called from listdata where id=(select id from lists where name='" + sel + "')");
    if (data == null) {
      data = new String[][] {};
    }
    listView.setModel(new DefaultTableModel(data, cols));
  }

  public boolean isConfigValid() {
    if (xfer_digit.getText().length() != 1) {setStatus("Error:Transfer digit is invalid"); return false;}
    for(int a=0;a<NUMQUESTIONS;a++) {
      if (questions.action[a] == A_GOTOUSER) {
        for(int b=0;b<9;b++) {
          if (questions.gotouser[a][b] != 0) break;
          if (b==8) {
            setStatus("Error:Question #" + (a+1) + " has an undefined goto action.");
            return false;
          }
        }
      }
      if (questions.action[a] == A_GOTOIDX) {
        if (questions.gotoidx[a] == 0) {
          setStatus("Error:Question #" + (a+1) + " has an undefined goto action.");
          return false;
        }
      }
    }
    if (sip_host.getText().length() == 0) {
      setStatus("Error:SIP host undefined.");
      return false;
    }
    if (sip_user.getText().length() == 0) {
      setStatus("Error:SIP user undefined.");
      return false;
    }
    int cnt = 0;
    try {
      int start = Integer.valueOf(sip_start.getText());
      if (start < 1024 || start > 60000) {
        setStatus("Error:SIP Port start is not in range");
        return false;
      }
      int end = Integer.valueOf(sip_end.getText());
      if (end < 1024 || end > 60000) {
        setStatus("Error:SIP Port end is not in range");
        return false;
      }
      if (end < start || (end-start < 1000)) {
        setStatus("Error:SIP Port range is invalid");
        return false;
      }
      for(int a=0;a<NUMQUESTIONS;a++) {
        if (!questions.enabled[a]) continue;
        File test = new File(questions.wav[a]);
        if (!test.exists()) {
          setStatus("Error : Message #" + (a+1) + " WAV file doesn't exist.");
          return false;
        }
        cnt++;
      }
    } catch (Exception e) {
      setStatus("Error:" + e);
      return false;
    }
    if (cnt == 0) {
      setStatus("Error:No messages defined.");
      return false;
    }
    cnt = 0;
    if (enable_g729a.isSelected()) cnt++;
    if (enable_g711u.isSelected()) cnt++;
    if (enable_g711a.isSelected()) cnt++;
    if (cnt == 0) {
      setStatus("Error:Must enable at least one codec");
      return false;
    }
    return true;
  }

  public static class Settings implements Serializable {
    static final long serialVersionUID = 1000L;
    String sql_host, sql_user, sql_pass;  //obsolete (do not delete or old cfg will not load)
    String sip_name, sip_user, sip_auth, sip_host, sip_pass;
    String wav_filename;  //obsolete
    int numberLines = 8;
    String xfer;
    boolean disable_g729a;  //obsolete
    int delay = 100;
    boolean enable_xfer = false;
    char xfer_digit = '0';
    int maxRingTime = 60;  //in seconds
    int maxAttempts = 3;
    boolean disable_detect = false;
    boolean enable_g729a = true, enable_g711u = true, enable_g711a = false;
    boolean enable_reinvites = true;
    boolean check_update = true;
    int greetingThreshold = 1000, silenceThreshold = 1000, silenceDuration = 1000;
    //calling times
    boolean day[] = new boolean[] {false, true, true, true, true, true, false};
    String start[] = new String[] {"10:00", "10:00", "10:00", "10:00", "10:00", "10:00", "10:00"};
    String end[] = new String[] {"17:00", "17:00", "17:00", "17:00", "17:00", "17:00", "17:00"};
    //sip port range
    int sip_start, sip_end;
    boolean voicemail_hangup;
    boolean exit_on_close_window;
  }

  private Settings settings;

  public final void loadSetup() {
    try {
      FileInputStream fis = new FileInputStream(JF.getUserPath() + "/broadcast" + cfgSuffix + ".cfg");
      ObjectInputStream ois = new ObjectInputStream(fis);
      settings = (Settings)ois.readObject();
      fis.close();

      sip_name.setText(settings.sip_name);
      sip_user.setText(settings.sip_user);
      sip_auth.setText(settings.sip_auth);
      sip_host.setText(settings.sip_host);
      sip_pass.setText(settings.sip_pass);

      if (settings.sip_start == 0) {
        settings.sip_start = 6000;
        settings.sip_end = 9000;
      }

      sip_start.setText("" + settings.sip_start);
      sip_end.setText("" + settings.sip_end);

      number_lines.setValue(new Integer(settings.numberLines));
      xfer_number.setText(settings.xfer);
      if (settings.delay == 0) settings.delay = 100;
      delay.setValue(new Integer(settings.delay));
      enable_xfer.setSelected(settings.enable_xfer);
      xfer_digit.setText("" + settings.xfer_digit);
      if (settings.maxRingTime == 0) settings.maxRingTime = 60;
      maxRingTime.setValue(new Integer(settings.maxRingTime));
      if (settings.maxAttempts == 0) settings.maxAttempts = 3;
      maxAttempts.setValue(new Integer(settings.maxAttempts));

      enable_g729a.setSelected(settings.enable_g729a);
      enable_g711u.setSelected(settings.enable_g711u);
      enable_g711a.setSelected(settings.enable_g711a);
      enable_reinvites.setSelected(settings.enable_reinvites);
      check_update.setSelected(settings.check_update);
      exit_on_close_window.setSelected(settings.exit_on_close_window);

      human_vm_detect.setSelected(!settings.disable_detect);
      greeting_threshold.setText(Integer.toString(settings.greetingThreshold));
      silence_threshold.setText(Integer.toString(settings.silenceThreshold));
      silence_duration.setText(Integer.toString(settings.silenceDuration));
      voicemail_hangup.setSelected(settings.voicemail_hangup);

      if (settings.day == null) {
        settings.day = new boolean[] {false, true, true, true, true, true, false};
      }
      day1.setSelected(settings.day[0]);
      day2.setSelected(settings.day[1]);
      day3.setSelected(settings.day[2]);
      day4.setSelected(settings.day[3]);
      day5.setSelected(settings.day[4]);
      day6.setSelected(settings.day[5]);
      day7.setSelected(settings.day[6]);

      if (settings.start == null) {
        settings.start = new String[] {"10:00", "10:00", "10:00", "10:00", "10:00", "10:00", "10:00"};
      }
      start1.setText(settings.start[0]);
      start2.setText(settings.start[1]);
      start3.setText(settings.start[2]);
      start4.setText(settings.start[3]);
      start5.setText(settings.start[4]);
      start6.setText(settings.start[5]);
      start7.setText(settings.start[6]);

      if (settings.end == null) {
        settings.end = new String[] {"17:00", "17:00", "17:00", "17:00", "17:00", "17:00", "17:00"};
      }
      end1.setText(settings.end[0]);
      end2.setText(settings.end[1]);
      end3.setText(settings.end[2]);
      end4.setText(settings.end[3]);
      end5.setText(settings.end[4]);
      end6.setText(settings.end[5]);
      end7.setText(settings.end[6]);

      fis = new FileInputStream(JF.getUserPath() + "/broadcast-msgs" + cfgSuffix + ".cfg");
      ois = new ObjectInputStream(fis);
      questions = (Questions)ois.readObject();

      fis.close();
    } catch (FileNotFoundException e) {
      loadDefaults();
      setStatus("Loading default settings");
      JFLog.log("No config found");
    } catch (Exception e) {
      loadDefaults();
      setStatus("Load Settings Failed:" + e);
      JFLog.log(e);
    }
    validateOptions();
  }

  private void loadDefaults() {
    settings = new Settings();
    sip_start.setText("6000");
    sip_end.setText("9000");
    number_lines.setValue(new Integer(8));
    delay.setValue(new Integer(100));
    maxRingTime.setValue(new Integer(60));
    maxAttempts.setValue(new Integer(3));
    human_vm_detect.setSelected(true);
    greeting_threshold.setText("1000");
    silence_threshold.setText("1000");
    silence_duration.setText("2000");
    voicemail_hangup.setSelected(false);
    enable_reinvites.setSelected(true);
    enable_g729a.setSelected(true);
    enable_g711u.setSelected(true);
    enable_g711a.setSelected(false);
    check_update.setSelected(true);
    exit_on_close_window.setSelected(false);
    day1.setSelected(false);
    day2.setSelected(true);
    day3.setSelected(true);
    day4.setSelected(true);
    day5.setSelected(true);
    day6.setSelected(true);
    day7.setSelected(false);
    start1.setText("10:00");
    start2.setText("10:00");
    start3.setText("10:00");
    start4.setText("10:00");
    start5.setText("10:00");
    start6.setText("10:00");
    start7.setText("10:00");
    end1.setText("17:00");
    end2.setText("17:00");
    end3.setText("17:00");
    end4.setText("17:00");
    end5.setText("17:00");
    end6.setText("17:00");
    end7.setText("17:00");
  }

  private void validateTime(JTextField field, String def) {
    String txt = field.getText();
    String f[] = txt.split("[:]");
    if (f.length != 2) {
      field.setText(def);
      return;
    }
    int hr = JF.atoi(f[0]);
    int min = JF.atoi(f[1]);
    if (hr < 0 || hr > 24 || min < 0 || min > 59) {
      field.setText(def);
    }
  }

  private void validateOptions() {
    if ((Integer)number_lines.getValue() < 1) {
      number_lines.setValue(new Integer(1));
    }

    if ((Integer)delay.getValue() < 100) {
      delay.setValue(new Integer(100));
    }
    if ((Integer)delay.getValue() > 10000) {
      delay.setValue(new Integer(10000));
    }

    if ((Integer)maxRingTime.getValue() < 10) {
      maxRingTime.setValue(new Integer(10));
    }
    if ((Integer)maxRingTime.getValue() > 100) {
      maxRingTime.setValue(new Integer(100));
    }

    if ((Integer)maxAttempts.getValue() < 1) {
      maxAttempts.setValue(new Integer(1));
    }
    if ((Integer)maxAttempts.getValue() > 100) {
      maxAttempts.setValue(new Integer(100));
    }
    validateTime(start1, "10:00");
    validateTime(start2, "10:00");
    validateTime(start3, "10:00");
    validateTime(start4, "10:00");
    validateTime(start5, "10:00");
    validateTime(start6, "10:00");
    validateTime(start7, "10:00");
    validateTime(end1, "17:00");
    validateTime(end2, "17:00");
    validateTime(end3, "17:00");
    validateTime(end4, "17:00");
    validateTime(end5, "17:00");
    validateTime(end6, "17:00");
    validateTime(end7, "17:00");
    int value = JF.atoi(greeting_threshold.getText());
    if (value < 100 || value > 32000) greeting_threshold.setText("1000");
    value = JF.atoi(silence_threshold.getText());
    if (value < 100 || value > 32000) silence_threshold.setText("1000");
    value = JF.atoi(silence_duration.getText());
    if (value < 100 || value > 5000) silence_duration.setText("2000");
  }

  public void saveSetup() {
    validateOptions();
    saveQuestion();
    try {
      FileOutputStream fos = new FileOutputStream(JF.getUserPath() + "/broadcast" + cfgSuffix + ".cfg");
      ObjectOutputStream oos = new ObjectOutputStream(fos);
      settings = new Settings();
      settings.sip_name = sip_name.getText();
      settings.sip_user = sip_user.getText();
      settings.sip_auth = sip_auth.getText();
      settings.sip_host = sip_host.getText();
      settings.sip_pass = sip_pass_getText();
      settings.sip_start = Integer.valueOf(sip_start.getText());
      settings.sip_end = Integer.valueOf(sip_end.getText());
      settings.wav_filename = "";  //obsolete
      settings.numberLines = (Integer)(number_lines.getValue());
      settings.xfer = xfer_number.getText();
      settings.disable_detect = !human_vm_detect.isSelected();
      settings.greetingThreshold = JF.atoi(greeting_threshold.getText());
      settings.silenceThreshold = JF.atoi(silence_threshold.getText());
      settings.silenceDuration = JF.atoi(silence_duration.getText());
      settings.voicemail_hangup = voicemail_hangup.isSelected();
      settings.delay = (Integer)(delay.getValue());
      settings.enable_xfer = enable_xfer.isSelected();
      settings.xfer_digit = xfer_digit.getText().charAt(0);
      settings.maxRingTime = (Integer)(maxRingTime.getValue());
      settings.maxAttempts = (Integer)(maxAttempts.getValue());
      settings.enable_g729a = enable_g729a.isSelected();
      settings.enable_g711u = enable_g711u.isSelected();
      settings.enable_g711a = enable_g711a.isSelected();
      settings.enable_reinvites = enable_reinvites.isSelected();
      settings.check_update = check_update.isSelected();
      settings.day[0] = day1.isSelected();
      settings.day[1] = day2.isSelected();
      settings.day[2] = day3.isSelected();
      settings.day[3] = day4.isSelected();
      settings.day[4] = day5.isSelected();
      settings.day[5] = day6.isSelected();
      settings.day[6] = day7.isSelected();
      settings.start[0] = start1.getText();
      settings.start[1] = start2.getText();
      settings.start[2] = start3.getText();
      settings.start[3] = start4.getText();
      settings.start[4] = start5.getText();
      settings.start[5] = start6.getText();
      settings.start[6] = start7.getText();

      settings.end[0] = end1.getText();
      settings.end[1] = end2.getText();
      settings.end[2] = end3.getText();
      settings.end[3] = end4.getText();
      settings.end[4] = end5.getText();
      settings.end[5] = end6.getText();
      settings.end[6] = end7.getText();
      oos.writeObject(settings);
      fos.close();
      fos = new FileOutputStream(JF.getUserPath() + "/broadcast-msgs" + cfgSuffix + ".cfg");
      oos = new ObjectOutputStream(fos);
      oos.writeObject(questions);
      fos.close();
    } catch (Exception e) {
      setStatus("Save Settings Failed:" + e);
      JFLog.log(e);
      return;
    }
    setStatus("OK : saved");
  }

  public int firstlocalsipport = 6000;
  public int nextlocalsipport = 6000;
  public int lastlocalsipport = 9000;

  public int getlocalsipport() {
    int ret = nextlocalsipport;
    nextlocalsipport++;
    if (nextlocalsipport == lastlocalsipport) nextlocalsipport = firstlocalsipport;
    return ret;
  }

  public String getHost(String host) {
    int idx = host.indexOf(":");
    if (idx == -1) return host;
    return host.substring(0, idx);
  }

  public int getPort(String host, int def) {
    int idx = host.indexOf(":");
    if (idx == -1) return def;
    return Integer.valueOf(host.substring(idx+1));
  }

  public static class Lines {
    boolean inuse = false;  //this line in use?
    boolean ringing = false;  //is line ringing
    boolean talking = false;  //are we talking to someone on a line
    boolean xfered = false;  //was call transfered
    boolean hello = false;  //did we hear something on the line
    boolean repeat = false;  //repeating message
    String number;  //number we called
    String callid;  //not caller id, but a unique ID for this call leg
    String survey;  //user responses (long responses are in brackets)
    String multi;  //for long inputs
    RTP rtp;
    int msgidx = 0;
    int wavpos = 0;
    int ringwait = 0;
    int postwait = 0;
    int quietcount = 0;  //wait till we hear quiet after hello (answering machine detection)
    int noresponse = 0;
    int repeatcount = 0;
    boolean voicemail = false;
  }

  public void startCalling() {
    if (!isConfigValid()) return;
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) {
      setStatus("Pick a valid list(1)");
      return;
    }
    saveSetup();
    firstlocalsipport = settings.sip_start;
    nextlocalsipport = firstlocalsipport;
    lastlocalsipport = settings.sip_end;
    skipCount = 0;
    maxquietcount = settings.silenceDuration / 20;
    //lock resources
    setState(false);
    if (sip_pass_getText().length() == 0)
      setStatus("waiting for register");
    else
      setStatus("calling");

    JFLog.log("thresholds : " + settings.greetingThreshold + "," + settings.silenceThreshold + "," + settings.silenceDuration);
    fixList(true, false);

    //load WAV file(s)
    wav = new Wav[NUMQUESTIONS];
    int cnt = 0;
    for(int a=0;a<NUMQUESTIONS;a++) {
      wav[a] = new Wav();
      if (!questions.enabled[a]) continue;
      if (!wav[a].load(questions.wav[a])) {
        setStatus(wav[a].errmsg);
        setState(true);
        return;
      }
      cnt++;
    }

    if (cnt == 0) {
      setStatus("no messages");
      setState(true);
      return;
    }

    //create persistant sql connection
    psql = new SQL();
    if (!psql.connect("jdbc:derby:" + databaseName)) {
      setStatus("SQL connection failed");
      setState(true);
      return;
    }

    listid = getid(psql, sel);
    if (listid == null) {
      setStatus("Error : Pick a valid list");
      psql.close();
      setState(true);
      return;
    }

    state = states.CALLING;
    start.setText("Stop");
    //create SIP resources
    lineCount = (Integer)number_lines.getValue();
    sip = new SIPClient();
    int fail = 0;
    while (!sip.init(getHost(sip_host.getText()), getPort(sip_host.getText(), 5060), getlocalsipport(), this, TransportType.UDP)) {
      fail++;
      if (fail == 10) {
        setStatus("Error:SIP init failed");
        setState(true);
        state = states.STOPPED;
        start.setText("Start!");
        return;
      }
    }
    if (sip_pass_getText().length() > 0) {
      sip.register(sip_name.getText(), sip_user.getText(), sip_auth.getText(), sip_pass_getText());
      registered = false;
    } else {
      registered = true;
    }
    lines = new Lines[lineCount];
    for(int a=0;a<lineCount;a++) {
      lines[a] = new Lines();
    }
    //create 20ms timer
    java.util.Timer timer = new java.util.Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
      public void run() {
        process();
      }
    }, 0, 20);

    int delayms = (Integer)delay.getValue();

    while (state == states.CALLING) {
      //look for a free SIP resource and fill it with a call
      JF.sleep(delayms);
      processUpdateList();
      if (!inCallingTime()) {
        JF.sleep(250);
        continue;
      }
      String number = nextAvailableNumber();  //NOTE:this keeps sql connection established (keep alive)
      if (!registered) continue;
      if (number == null) {
        //no more numbers to dial
        if (anyStillCalling()) continue;
        setStatus("Finished list");
        break;
      }
      for(int a=0;a<lineCount;a++) {
        if (lines[a].inuse) continue;
        lines[a].rtp = null;
        lines[a].msgidx = 0;
        lines[a].wavpos = 0;
        lines[a].ringwait = 0;
        lines[a].postwait = 0;
        lines[a].xfered = false;
        lines[a].hello = false;
        lines[a].repeat = false;
        lines[a].number = number;
        lines[a].quietcount = 0;
        lines[a].repeatcount = 0;
        lines[a].voicemail = false;
        lines[a].survey = "";
        lines[a].multi = "";
        lines[a].inuse = true;
        lines[a].callid = dialNumber(number, lines[a]);
        markCalling(number);
        break;
      }
    }
    while (anyStillCalling()) {
      processUpdateList();
      JF.sleep(1000);  //wait for all calls to finish
    }
    processUpdateList();
    psql.close();
    timer.cancel();
    //unlock resources
    setState(true);
    if (state == states.STOPPING) {
      setStatus("stopped");
    }
    sip.unregister();
    sip.uninit();
    state = states.STOPPED;
    start.setText("Start!");
  }

  public void stopCalling() {
    state = states.STOPPING;
    setStatus("stopping");
  }

  public void forceStopCalling() {
    setStatus("killing connections");
    for(int a=0;a<lines.length;a++) {
      if (lines[a].inuse) {
        sip.bye(lines[a].callid);
        endCall(lines[a], "err_force_stop");
      }
    }
  }

  public boolean inCallingTime() {
    Calendar c = Calendar.getInstance();
    int cday = c.get(Calendar.DAY_OF_WEEK);  //1-7
    cday--;  //0-6
    int chr = c.get(Calendar.HOUR_OF_DAY);  //0-23
    int cmin = c.get(Calendar.MINUTE);  //0-59
    if (!settings.day[cday]) return false;
    String start = settings.start[cday];
    String startf[] = start.split("[:]");
    int starthr = JF.atoi(startf[0]);
    int startmin = JF.atoi(startf[1]);
    if (chr < starthr) return false;
    if (chr == starthr && cmin < startmin) return false;
    String end = settings.end[cday];
    String endf[] = end.split("[:]");
    int endhr = JF.atoi(endf[0]);
    int endmin = JF.atoi(endf[1]);
    if (chr > endhr) return false;
    if (chr == endhr && cmin > endmin) return false;
    return true;
  }

  public void setState(boolean state) {
    sip_host.setEnabled(state);
    sip_auth.setEnabled(state);
    sip_name.setEnabled(state);
    sip_user.setEnabled(state);
    sip_pass.setEnabled(state);
    sip_start.setEnabled(state);
    sip_end.setEnabled(state);
    save_setup.setEnabled(state);
    create_list.setEnabled(state);
    delete_list.setEnabled(state);
    selected_list.setEnabled(state);
    import_list.setEnabled(state);
    number_lines.setEnabled(state);
    xfer_number.setEnabled(state);
    reset_list.setEnabled(state);
    list_name.setEnabled(state);
    enable_g729a.setEnabled(state);
    enable_g711u.setEnabled(state);
    enable_g711a.setEnabled(state);
    enable_reinvites.setEnabled(state);
    check_update.setEnabled(state);
    exit_on_close_window.setEnabled(state);
    human_vm_detect.setEnabled(state);
    greeting_threshold.setEnabled(state);
    silence_threshold.setEnabled(state);
    silence_duration.setEnabled(state);
    voicemail_hangup.setEnabled(state);
    export_list.setEnabled(state);
    delay.setEnabled(state);
    enable_xfer.setEnabled(state);
    xfer_digit.setEnabled(state);
    fix_list.setEnabled(state);
    enable.setEnabled(state);
    wav_filename.setEnabled(state);
    select_file.setEnabled(state);
    single.setEnabled(state);
    multi.setEnabled(state);
    count.setEnabled(state);
    cont.setEnabled(state);
    terminate.setEnabled(state);
    gotouser.setEnabled(state);
    gotoidx.setEnabled(state);
    gotoidxnum.setEnabled(state);
    goto_1.setEnabled(state);
    goto_2.setEnabled(state);
    goto_3.setEnabled(state);
    goto_4.setEnabled(state);
    goto_5.setEnabled(state);
    goto_6.setEnabled(state);
    goto_7.setEnabled(state);
    goto_8.setEnabled(state);
    goto_9.setEnabled(state);
    maxRingTime.setEnabled(state);
    maxAttempts.setEnabled(state);
    xfer.setEnabled(state);
    day1.setEnabled(state);
    day2.setEnabled(state);
    day3.setEnabled(state);
    day4.setEnabled(state);
    day5.setEnabled(state);
    day6.setEnabled(state);
    day7.setEnabled(state);
    start1.setEnabled(state);
    start2.setEnabled(state);
    start3.setEnabled(state);
    start4.setEnabled(state);
    start5.setEnabled(state);
    start6.setEnabled(state);
    start7.setEnabled(state);
    end1.setEnabled(state);
    end2.setEnabled(state);
    end3.setEnabled(state);
    end4.setEnabled(state);
    end5.setEnabled(state);
    end6.setEnabled(state);
    end7.setEnabled(state);
  }

  public String nextAvailableNumber() {
    String data[][];
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return null;
    //select next available number where status='new' or 'busy'.  order by status desc to get 'new' before 'busy'
    data = psql.select("select number from listdata where (status = 'new' or status = 'busy') and attempts < " + settings.maxAttempts + " and id=" + listid + " order by status desc, attempts asc fetch first row only");
    if (data == null) {
      setStatus("nextAvailableNumber : select failed (1)");
      return null;
    }
    if (data.length == 0) return null;
    return data[0][0];
  }

  public Codec[] getCodecs() {
    Codec codecs[] = new Codec[0];
    if (enable_g729a.isSelected()) codecs = SIP.addCodec(codecs, RTP.CODEC_G729a);
    if (enable_g711u.isSelected()) codecs = SIP.addCodec(codecs, RTP.CODEC_G711u);
    if (enable_g711a.isSelected()) codecs = SIP.addCodec(codecs, RTP.CODEC_G711a);
    return codecs;
  }

  public String dialNumber(String number, Lines line) {
    //generate a callid and initiate a sip call
    line.rtp = new RTP();
    line.rtp.init(this);
    SDP sdp = new SDP();
    SDP.Stream astream = sdp.addStream(SDP.Type.audio);
    astream.port = line.rtp.getlocalrtpport();
    astream.codecs = getCodecs();
//    if (allowdups) {
      int idx = number.indexOf(",");
      if (idx != -1) {
        number = number.substring(0, idx);
      }
//    }
    return sip.invite(number, sdp);
  }

  public void markCalling(String number) {
    String data[][];
    //set status='calling' and increment attempts
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return;
    int attempts = 0;
    data = psql.select("select attempts from listdata where number = '" + number + "' and id=" + listid);
    if (data == null) {
      setStatus("select failed");
      return;
    }
    if (data.length == 0) return;
    attempts = Integer.valueOf(data[0][0]) + 1;
    if (!psql.execute("update listdata set attempts = " + attempts + ", status = 'calling' where number = '" + number + "' and id=" + listid)) {
      setStatus("update failed");
      return;
    }
    //update local copy
    int rc = listView.getRowCount();
    for(int row=0;row<rc;row++) {
      if (((String)listView.getValueAt(row, 0)).equals(number)) {
        listView.setValueAt("calling", row, 1);
        listView.setValueAt(new Integer(attempts), row, 2);
        listView.repaint();
        return;
      }
    }
  }

  public void unmarkCalling() {
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return;
    if (!psql.execute("update lists set status = 'aborted', called = current_timestamp where status = 'calling' and id=" + listid)) {
      setStatus("update failed");
      return;
    }
    updateList();
  }

  public boolean anyStillCalling() {
    String data[][];
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return false;
    data = psql.select("select count(number) from listdata where status = 'calling' and id=" + listid);
    if (data == null) {
      setStatus("select failed");
      return false;
    }
    if (data.length == 0) return false;
    return !(data[0][0].equals("0"));
  }

  public void endCall(Lines line, String status) {
    System.out.println("endCall:" + line.number + ":status=" + status);
    line.talking = false;
    line.rtp.stop();
    line.rtp.uninit();
    line.rtp = null;
    line.callid = null;
    line.noresponse = 0;
    line.inuse = false;
    updateList.add(new update(line.number, status, line.survey));
  }

  public void processUpdateList() {
    String sel = (String)selected_list.getSelectedItem();
    if (sel == null || sel.length() == 0) return;
    update u;
    while (updateList.size() > 0) {
      u = updateList.remove(0);
      if (!psql.execute("update listdata set status = '" + u.status + "', survey = '" + u.survey + "', called = current_timestamp where number = '" + u.number + "' and id=" + listid)) {
        setStatus("endCall : update failed : " + u.number);
        return;
      }
      String called = psql.select1value("select called from listdata where number = '" + u.number + "' and id=" + listid);
      //update local copy
      int rc = listView.getRowCount();
      for(int row=0;row<rc;row++) {
        if (((String)listView.getValueAt(row, 0)).equals(u.number)) {
          listView.setValueAt(u.status, row, 1);
          listView.setValueAt(u.survey, row, 3);
          listView.setValueAt(called, row, 4);
          listView.repaint();
          break;
        }
      }
    }
  }

  public void process() {
    //20ms audio timer
    byte encoded[];
    if (processing) {
      skipCount++;
      setStatus("audio timer skipped : system too slow : reduce # of lines : skipCount=" + skipCount);
      return;
    }
    processing = true;
    try {
      for(int a=0;a<lineCount;a++) {
        if (!lines[a].inuse) continue;
        if (!lines[a].talking) {
          lines[a].ringwait++;
          if (lines[a].ringwait == 50 * settings.maxRingTime) {  //there are 50 process() per second
            //waiting/ringing for too long (1min)
            sip.cancel(lines[a].callid);
            endCall(lines[a], "no_anwser");
          }
          continue;
        }
        if (lines[a].quietcount < maxquietcount) {
          if (!lines[a].hello) {
            //detect hello/answering machine
            if (lines[a].rtp.getDefaultChannel().getSamples(tmp)) {
              for(int p=0;p<160;p++) {
                if (Math.abs(tmp[p]) > settings.greetingThreshold) {
                  lines[a].hello = true;
                  break;
                }
              }
            }
          } else {
            //detect end of hello/answering machine beep
            if (lines[a].rtp.getDefaultChannel().getSamples(tmp)) {
              int avg = 0;
              int peek = 0;
              int val;
              for(int p=0;p<160;p++) {
                val = Math.abs(tmp[p]);
                avg += val;
                if (val > peek) peek = val;
              }
              avg /= 160;
              if (avg > settings.silenceThreshold) {
                lines[a].quietcount = 0;
                if (lines[a].voicemail) continue;
                if (isVoiceMailBeep(tmp, peek)) {
                  if (settings.voicemail_hangup) {
                    JFLog.log("voicemail detected (hangup) : " + lines[a].number);
                    sip.bye(lines[a].callid);
                    endCall(lines[a], "voicemail");
                    continue;
                  } else {
                    lines[a].voicemail = true;
                  }
                }
              } else {
                if (lines[a].voicemail) {
                  //silence after voicemail beep detected
                  JFLog.log("voicemail detected : " + lines[a].number);
                  lines[a].quietcount = maxquietcount;
                } else {
                  lines[a].quietcount++;
                }
              }
            } else {
              lines[a].quietcount++;    //g729a silence suppression (evil)
            }
          }
          encoded = lines[a].rtp.getDefaultChannel().coder.encode(silence);
          lines[a].rtp.getDefaultChannel().writeRTP(encoded, 0, encoded.length);
        } else {
          if (lines[a].wavpos >= wav[lines[a].msgidx].samples.length) {
            if (questions.action[lines[a].msgidx] == A_CONT) {
              nextMessage(lines[a]);
              continue;
            }
            if (questions.action[lines[a].msgidx] == A_GOTOIDX) {
              lines[a].msgidx = questions.gotoidx[lines[a].msgidx] - 1 - 1;
              nextMessage(lines[a]);
              continue;
            }
            if (questions.action[lines[a].msgidx] == A_XFER) {
              if (lines[a].xfered) continue;  //already tried to xfer call
              if (xfer_number.getText().length() == 0) continue;  //no # to transfer to
              sip.refer(lines[a].callid, xfer_number.getText());
              lines[a].xfered = true;
              continue;
            }
            lines[a].postwait += 160;
            if (lines[a].postwait == 40000) {  //5 seconds
              if ((questions.action[lines[a].msgidx] == A_TERMINATE) || (questions.action[lines[a].msgidx] == A_XFER) || (lines[a].noresponse == 5)) {
                sip.bye(lines[a].callid);
                endCall(lines[a], "ok");
                continue;
              } else {
                if (questions.action[lines[a].msgidx] == A_MULTI) {
                  if (lines[a].multi.length() > 0) {
                    lines[a].survey += "(" + lines[a].multi + ")";
                    nextMessage(lines[a]);
                  } else {
                    //repeat message
                    lines[a].repeatcount++;
                    if (lines[a].repeatcount == 3) {
                        sip.bye(lines[a].callid);
                        endCall(lines[a], "ok");
                        continue;
                    }
                    lines[a].repeat = true;  //allow immediate response
                    lines[a].wavpos = 0;
                    lines[a].postwait = 0;
                    lines[a].multi = "";
                    lines[a].noresponse++;
                  }
                } else if (questions.action[lines[a].msgidx] == A_GOTOUSER) {
                  //repeat message
                  lines[a].repeatcount++;
                  if (lines[a].repeatcount == 3) {
                      sip.bye(lines[a].callid);
                      endCall(lines[a], "ok");
                      continue;
                  }
                  lines[a].repeat = true;  //allow immediate response
                  lines[a].wavpos = 0;
                  lines[a].postwait = 0;
                  lines[a].noresponse++;
                }
              }
            }
            encoded = lines[a].rtp.getDefaultChannel().coder.encode(silence);
            lines[a].rtp.getDefaultChannel().writeRTP(encoded, 0, encoded.length);
          } else {
            encoded = lines[a].rtp.getDefaultChannel().coder.encode(Arrays.copyOfRange(wav[lines[a].msgidx].samples, lines[a].wavpos, lines[a].wavpos + 160));
            lines[a].rtp.getDefaultChannel().writeRTP(encoded, 0, encoded.length);
            lines[a].wavpos += 160;
          }
        }
      }
    } catch (Exception e) {
      JFLog.log("process() exception:");
      JFLog.log(e);
    }
    processing = false;
  }

  public void checkVersion() {
    try {
      BufferedReader reader = new BufferedReader(new InputStreamReader(
        new URL("http://jfbroadcast.sourceforge.net/version.php").openStream()));
      String line = reader.readLine();
      if (line.equals(version)) {JFLog.log("version is up-to-date"); return;}
      JFLog.log("newer version is available : " + line);
      JOptionPane.showMessageDialog(this,
        "A newer version of jfbroadcast is available! (v" + line + ")\r\nPlease goto http://jfbroadcast.sourceforge.net to download it",
        "Info",
        JOptionPane.INFORMATION_MESSAGE);
    } catch (Exception e) {
      JFLog.log("error while checking for version update");
      JFLog.log(e);
    }
  }

  //SIPClientInterface

  public void onRegister(SIPClient sip, boolean status) {
    if (status) {
      if (state != states.CALLING) return;
      registered = true;
      setStatus("calling");
    } else {
      if (state != states.CALLING) return;
      setStatus("Error : register failed");
    }
  }

  public void onTrying(SIPClient sip, String callid) {
    if (state == states.STOPPED) return;
    for(int a=0;a<lineCount;a++) {
      if (!lines[a].inuse) continue;
      if (!lines[a].callid.equals(callid)) continue;
      JFLog.log("trying : " + lines[a].number);
    }
  }

  public void onRinging(SIPClient sip, String callid) {
    if (state == states.STOPPED) return;
    for(int a=0;a<lineCount;a++) {
      if (!lines[a].inuse) continue;
      if (!lines[a].callid.equals(callid)) continue;
      lines[a].ringing = true;
      JFLog.log("ringing : " + lines[a].number);
    }
  }

  public void onSuccess(SIPClient sip, String callid, SDP sdp, boolean complete) {
    if (!complete) return;  //not interested in 183 (ringback)
    if (state == states.STOPPED) return;
    SDP.Stream astream = sdp.getFirstAudioStream();
    if (astream == null) return;
    for(int a=0;a<lineCount;a++) {
      if (!lines[a].inuse) continue;
      if (!lines[a].callid.equals(callid)) continue;
      if (!lines[a].talking) {
        int cnt = 0;
        if (astream.hasCodec(RTP.CODEC_G729a)) cnt++;
        if (astream.hasCodec(RTP.CODEC_G711u)) cnt++;
        if (astream.hasCodec(RTP.CODEC_G711a)) cnt++;
        if ((cnt > 1) && (enable_reinvites.isSelected())) {
          Codec codecs[] = new Codec[0];
          //try to reinvite with one codec
          if (astream.hasCodec(RTP.CODEC_G729a)) {
            codecs = new Codec[] {RTP.CODEC_G729a};
          }
          else if (SIP.hasCodec(codecs, RTP.CODEC_G711u)) {
            codecs = new Codec[] {RTP.CODEC_G711u};
          }
          else if (SIP.hasCodec(codecs, RTP.CODEC_G711a)) {
            codecs = new Codec[] {RTP.CODEC_G711a};
          }
          SDP localsdp = new SDP();
          SDP.Stream localStream = localsdp.addStream(SDP.Type.audio);
          localStream.codecs = codecs;
          localStream.port = lines[a].rtp.getlocalrtpport();
JFLog.log("reinvite:" + lines[a].number);
          sip.reinvite(callid, localsdp);
          return;
        }
JFLog.log("connected : number=" + lines[a].number);
        lines[a].rtp.start();
        RTPChannel channel = lines[a].rtp.createChannel(astream);
        if (channel == null) {
          JFLog.log("Failed to create RTPChannel:" + lines[a].number);
          return;
        };
        channel.start();
        lines[a].ringing = false;
        if (settings.disable_detect) {
          lines[a].hello = true;
          lines[a].quietcount = maxquietcount;
        }
        lines[a].talking = true;
        return;
      }
    }
  }

  public void onBye(SIPClient sip, String callid) {
    if (state == states.STOPPED) return;
    for(int a=0;a<lineCount;a++ ){
      if (!lines[a].inuse) continue;
      if (lines[a].callid.equals(callid)) {
        endCall(lines[a], "ok");
      }
    }
  }

  public int onInvite(SIPClient sip, String callid, String fromid, String fromnumber, SDP sdp) {
    return 486;  //busy
  }

  public void onCancel(SIPClient sip, String callid, int code) {
    if (state == states.STOPPED) return;
    for(int a=0;a<lineCount;a++ ){
      if (!lines[a].inuse) continue;
      if (lines[a].callid.equals(callid)) {
        switch (code) {
          case 486: endCall(lines[a], "busy"); break;
          default: endCall(lines[a], "err_" + code); break;
        }
      }
    }
  }

  public void onRefer(SIPClient sip, String callid) {
    if (state == states.STOPPED) return;
    for(int a=0;a<lineCount;a++ ){
      if (!lines[a].inuse) continue;
      if (lines[a].callid.equals(callid)) {
        endCall(lines[a], "ok_xfered");
      }
    }
  }

  public void onNotify(SIPClient sip, String callid, String event, String content) {
  }

  public void onAck(SIPClient sip, String callid, SDP sdp) {
  }

  //RTPInterface

  public void rtpSamples(RTPChannel rtp) {}

  public void rtpDigit(RTPChannel channel, char c) {
    if (state == states.STOPPED) return;
    if (c == '*') return;
    for(int a=0;a<lineCount;a++ ){
      if (!lines[a].inuse) continue;
      if (lines[a].rtp == channel.rtp) {
        lines[a].noresponse = 0;
        if ((enable_xfer.isSelected()) && (c == xfer_digit.getText().charAt(0))) {
          if (lines[a].xfered) return;  //already tried to xfer call
          if (xfer_number.getText().length() == 0) return;
          sip.refer(lines[a].callid, xfer_number.getText());
          lines[a].xfered = true;
          return;
        }
        if ((lines[a].wavpos < 8000 * 5) && (!lines[a].repeat)) {
          return;  //too soon (msg must play at least 5 sec before response is allowed)
        }
        switch (questions.action[lines[a].msgidx]) {
          case A_TERMINATE:
            break;
          case A_SINGLE:
            lines[a].survey += c;
            nextMessage(lines[a]);
            break;
          case A_MULTI:
            if (c == '#') {
              lines[a].survey += "(" + lines[a].multi + ")";
              lines[a].repeatcount = 0;
              nextMessage(lines[a]);
            } else {
              lines[a].multi += c;
            }
            lines[a].postwait = 0;
            break;
          case A_GOTOUSER:
            if (c == '#') return;  //bad input
            if (c == '0') return;  //bad input
            int newidx = questions.gotouser[lines[a].msgidx][c - '0' - 1];
            if ((newidx < 1) || (newidx > 99)) return;  //bad input
            lines[a].survey += c;  //see bug # 2 @ jfbroadcast.sf.net
            lines[a].msgidx = newidx - 1 - 1;  //-1 for zero based, -1 for nextMessage()
            lines[a].repeatcount = 0;
            nextMessage(lines[a]);
            break;
        }
      }
    }
  }

  public void rtpH263(RTPChannel rtp, byte[] bytes, int off, int len) {}
  public void rtpH263_1998(RTPChannel rtp, byte[] bytes, int off, int len) {}
  public void rtpH263_2000(RTPChannel rtp, byte[] bytes, int off, int len) {}
  public void rtpH264(RTPChannel rtp, byte[] bytes, int off, int len) {}
  public void rtpJPEG(RTPChannel rtp, byte[] bytes, int off, int len) {}
  public void rtpVP8(RTPChannel rtp, byte[] bytes, int off, int len) {}
  public void rtpInactive(RTPChannel rtp) {}

  private void nextMessage(Lines line) {
    line.wavpos = 0;
    line.postwait = 0;
    line.multi = "";
    line.repeat = false;
    while (true) {
      if (line.msgidx == NUMQUESTIONS-1) {
        sip.bye(line.callid);
        endCall(line, "ok");
        return;
      }
      line.msgidx++;
      if (line.msgidx < 0) continue;
//      System.out.println("nextMsg=" + line.msgidx);
      if (questions.enabled[line.msgidx]) return;
    }
  }

  public void rtpPacket(RTPChannel rtp, byte[] data,int pos,int len) {}
  public void rtcpPacket(RTPChannel rtp, byte[] data,int pos,int len) {}

  public static class Questions implements Serializable {
    static final long serialVersionUID = 1000L;
    boolean enabled[] = new boolean[NUMQUESTIONS];
    String wav[] = new String[NUMQUESTIONS];
    int action[] = new int[NUMQUESTIONS];
    int count[] = new int[NUMQUESTIONS];  //# digits allowed for single entry
    int gotouser[][] = new int[NUMQUESTIONS][9];
    int gotoidx[] = new int[NUMQUESTIONS];
    Questions() {
      for(int a=0;a<NUMQUESTIONS;a++) {
        enabled[a] = false;
        wav[a] = new String();
        action[a] = A_CONT;
        count[a] = 0;
      }
    }
  }

  public static Questions questions = new Questions();
  private int idx = 0;

  public void selectFile() {
    JFileChooser chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getCurrentPath()));
    if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return;
    wav_filename.setText(chooser.getSelectedFile().toString().replaceAll("\\\\", "/"));
  }

  private void prevQuestion() {
    if (idx == 0) return;
    saveQuestion();
    idx--;
    loadQuestion();
    try { msg_idx.setValue(idx); } catch (Exception e) {System.out.println("" + e);}
  }

  private void nextQuestion() {
    if (idx == NUMQUESTIONS-1) return;
    saveQuestion();
    idx++;
    loadQuestion();
    try { msg_idx.setValue(idx); } catch (Exception e) {System.out.println("" + e);}
  }

  private void gotoQuestion(int newidx) {
    saveQuestion();
    idx = newidx;
    loadQuestion();
  }

  private void loadQuestion() {
    title.setText("#" + (idx+1) + "/" + NUMQUESTIONS);
    enable.setSelected(questions.enabled[idx]);
    wav_filename.setText(questions.wav[idx]);
    cont.setSelected(questions.action[idx] == A_CONT);
    single.setSelected(questions.action[idx] == A_SINGLE);
    multi.setSelected(questions.action[idx] == A_MULTI);
    terminate.setSelected(questions.action[idx] == A_TERMINATE);
    gotouser.setSelected(questions.action[idx] == A_GOTOUSER);
    gotoidx.setSelected(questions.action[idx] == A_GOTOIDX);
    xfer.setSelected(questions.action[idx] == A_XFER);
    count.setSelectedIndex(questions.count[idx]);
    goto_1.setText("" + questions.gotouser[idx][0]);
    goto_2.setText("" + questions.gotouser[idx][1]);
    goto_3.setText("" + questions.gotouser[idx][2]);
    goto_4.setText("" + questions.gotouser[idx][3]);
    goto_5.setText("" + questions.gotouser[idx][4]);
    goto_6.setText("" + questions.gotouser[idx][5]);
    goto_7.setText("" + questions.gotouser[idx][6]);
    goto_8.setText("" + questions.gotouser[idx][7]);
    goto_9.setText("" + questions.gotouser[idx][8]);
    gotoidxnum.setText("" + questions.gotoidx[idx]);
  }

  private void saveQuestion() {
    questions.enabled[idx] = enable.isSelected();
    questions.wav[idx] = wav_filename.getText();
    if (cont.isSelected()) questions.action[idx] = A_CONT;
    if (single.isSelected()) questions.action[idx] = A_SINGLE;
    if (multi.isSelected()) questions.action[idx] = A_MULTI;
    if (terminate.isSelected()) questions.action[idx] = A_TERMINATE;
    if (gotouser.isSelected()) questions.action[idx] = A_GOTOUSER;
    if (gotoidx.isSelected()) questions.action[idx] = A_GOTOIDX;
    if (xfer.isSelected()) questions.action[idx] = A_XFER;
    questions.count[idx] = count.getSelectedIndex();
    questions.gotouser[idx][0] = Math.min(Math.abs(JF.atoi(goto_1.getText())), 99);
    questions.gotouser[idx][1] = Math.min(Math.abs(JF.atoi(goto_2.getText())), 99);
    questions.gotouser[idx][2] = Math.min(Math.abs(JF.atoi(goto_3.getText())), 99);
    questions.gotouser[idx][3] = Math.min(Math.abs(JF.atoi(goto_4.getText())), 99);
    questions.gotouser[idx][4] = Math.min(Math.abs(JF.atoi(goto_5.getText())), 99);
    questions.gotouser[idx][5] = Math.min(Math.abs(JF.atoi(goto_6.getText())), 99);
    questions.gotouser[idx][6] = Math.min(Math.abs(JF.atoi(goto_7.getText())), 99);
    questions.gotouser[idx][7] = Math.min(Math.abs(JF.atoi(goto_8.getText())), 99);
    questions.gotouser[idx][8] = Math.min(Math.abs(JF.atoi(goto_9.getText())), 99);
    questions.gotoidx[idx] = Math.min(Math.abs(JF.atoi(gotoidxnum.getText())), 99);
  }

  private void loadHelp(){
    try {
      InputStream is = getClass().getResourceAsStream("readme.txt");
      int len = is.available();
      byte txt[] = new byte[len];
      is.read(txt);
      help.setText(new String(txt,0,len));
      help.setCaretPosition(0);
    } catch (Exception e) { }
  }

  private void processArgs() {
    if (args == null || args.length == 0) return;
    for(int a=0;a<args.length;a++) {
      if (args[a].equals("-start")) {
        a++;
        if (a == args.length) {
          JFLog.log("Error:Must include list with -start option");
          return;
        }
        startList = args[a];
      }
      else if (args[a].equals("-dbpath")) {
        a++;
        if (a == args.length) {
          JFLog.log("Error:Must include path with -dbpath option");
          return;
        }
        dbPath = args[a];
      }
      else if (args[a].equals("-cfgsuffix")) {
        a++;
        if (a == args.length) {
          JFLog.log("Error:Must include string with -cfgsuffix option");
          return;
        }
        cfgSuffix = args[a];
      }
      else if (args[a].equals("-allowdups")) {
        allowdups = true;
      }
    }
  }
  private void setPosition() {
    Dimension d = getSize();
    Rectangle s = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
    if ((d.width > s.width) || (d.height > s.height)) {
      if (d.width > s.width) d.width = s.width;
      if (d.height > s.height) d.height = s.height;
      setSize(d);
    }
    setLocation(s.width/2 - d.width/2, s.height/2 - d.height/2);
  }
  public void actionPerformed(ActionEvent e) {
    Object o = e.getSource();
    if (o == exit) {
      if (lockFile != null) {
        lockFile.unlock();
      }
      System.exit(0);
    }
    if (o == show) {
      if (isVisible()) {
        if ((getExtendedState() & Frame.ICONIFIED) == Frame.ICONIFIED) {
          setExtendedState(getExtendedState() ^ Frame.ICONIFIED);
        }
        requestFocus();
      } else {
        setVisible(true);
      }
    }
  }

  private void setOptsState() {
    boolean state = human_vm_detect.isSelected();
    greeting_threshold.setEnabled(state);
    silence_threshold.setEnabled(state);
    silence_duration.setEnabled(state);
    voicemail_hangup.setEnabled(state);
  }

  //voicemail beep is 0.2 sec @ 987.8 Hz (B5 note)
  //volume is around 7000 (16bit)
  private boolean isVoiceMailBeep(short samples[], int peek) {
    //basically check if wave moves up/down smoothly and maintains a
    //relative constant volume
    int dir = 0;  //unknown
    short last = samples[1];
    if (peek < 2000) return false;  //too quiet
    if (peek > 30000) return false;  //too loud
    int pmax = peek + 1000;
    int pmin = peek - 1000;
    int nmax = -peek + 1000;
    int nmin = -peek - 1000;
    if (last > samples[0]) {
      dir = 1;
    } else {
      dir = -1;
    }
    int wc = -1;  //samples / wave count
    for(int a=2;a<160;a++) {
      short now = samples[a];
      if (dir == 1) {
        if (now < last) {
          //hit a peek
          if (wc != -1) {
            if (last < pmin || last > pmax) {
//              System.out.println("+last <> peek:" + last + "<" + pmin + ">" + pmax);
              return false;
            }
            if (wc < 7 || wc > 9) {
//              System.out.println("wc=" + wc);
              return false;
            }  //Hz out of range
          }
          wc = 0;  //start wave count
          dir = -1;
        }
      } else {
        if (now > last) {
          //hit valley
          if (wc != -1) {
            if (last < nmin || last > nmax) {
//              System.out.println("-last <> peek:" + last + "<" + nmin + ">" + nmax + ":wc=" + wc);
              return false;
            }
          }
          dir = 1;
        }
      }
      if (wc != -1) wc++;
      last = now;
    }
    return true;
  }

  public SystemTray tray;
  public TrayIcon icon;
  public MenuItem exit, show;
}
